package main

import (
	"bytes"
	"embed"
	"fmt"
	"github.com/urfave/cli/v2"
	"html/template"
	"log"
	"strings"
	"time"
)

var (
	//默认是当前目录下，即: asset/tpl 等价于 gen/asset/tpl
	//go:embed asset/tpl
	f    embed.FS
	data = make(map[string]*entityReq, 0)
	list []table
	cfg  config   //数据库配置
	app  *cli.App // cli对象
	dbc  *database
)

// CreateMarkdown 生成mysql markdown文档，这个方法不需要修改，同样适用其他SQL数据库!
func (m *schema) CreateMarkdown() (err error) {
	res := new(markDownData)
	i := 1
	for _, v := range m.doTables {
		res.TableList = append(res.TableList, &tableList{
			Index:          i,
			UpperTableName: m.ToUpper(v.Name),
			TableName:      v.Name,
			Comment:        v.Comment,
		})
		// 查询表结构信息
		desc := new(markDownDataChild)
		desc.List, err = m.ColumnInfo(v.Name)
		if err != nil {
			log.Fatal("markdown>>", err)
			continue
		}
		desc.Index = i
		desc.TableName = v.Name
		desc.Comment = v.Comment
		res.DescList = append(res.DescList, desc)
		i++
	}
	// 生成所有表的文件
	err = m.genMarkdown(res)
	if err != nil {
		return
	}
	return
}

// 生成表列表
func (m *schema) genMarkdown(data *markDownData) (err error) {
	// 写入markdown
	file := m.path + fmt.Sprintf("markdown_%s.md", time.Now().Format("2006-01-02 15:04:05"))
	tplByte, err := f.ReadFile(tplMarkdown)
	if err != nil {
		return
	}
	// 解析
	content := bytes.NewBuffer([]byte{})
	tpl, err := template.New("markdown").Parse(string(tplByte))
	err = tpl.Execute(content, data)
	if err != nil {
		return
	}
	// 表信息写入文件
	err = m.writeFileAppend(file, content.String()) // WriteAppendFile() 在 tool.go 中
	if err != nil {
		return
	}
	return
}

func (m *schema) CreateAllModel() {
	if cfg.Gen.Table != "" {
		tables := strings.Split(cfg.Gen.Table, ",")
		for _, v := range tables {
			m.genOneModel(data[v])
		}
	} else {
		for _, v := range data {
			m.genOneModel(v)
		}
	}
	m.genDaoInit(list)
}

func (m *schema) CreateAllDao() {
	if cfg.Gen.Table != "" {
		tables := strings.Split(cfg.Gen.Table, ",")
		for _, v := range tables {
			m.genOneDao(data[v])
		}
	} else {
		for _, v := range data {
			m.genOneDao(v)
		}
	}
	//m.genToml()
}

// genEntity 该函数只生成一个表结构体实体，注意:是只生成一个结构体! 对应 asset/tpl/entity.tpl，这个方法只被 CreateEntity()调用。
func (m *schema) genOneModel(req *entityReq) {
	s := fmt.Sprintf(`
	package %s
	import (
		"time"
	)
	`, pkgDao)
	s2 := fmt.Sprintf(`
package %s
`, pkgDao)
	req.EntityPath = m.createDir(m.path+pkgDao) + "/" + "model" + ".go"
	// 声明表结构变量，一个 TableInfo对象就是一个 Goland结构体
	t := &tableInfo{
		Table:        m.Capitalize(req.TableName),
		Table2:       req.TableName,
		TablePrefix:  req.TablePrefix,
		TableCamel:   m.Snake2Camel(req.TableName),
		TableComment: m.addToComment(req.TableComment, ""),
	}
	var (
		fieldType bool
		writeTime bool
	)
	// 装载单个表的所有字段，将单个表的所有字段保存到 TableInfo.Fields 中。
	for _, val := range req.TableColumns {
		tags := req.TagKeys
		if val.ColumnName == "version" || val.ColumnName == "delete_flag" || val.ColumnName == "deleted" {
			tags = append(tags, "gm")
		}
		if val.FieldType == "time.Time" {
			fieldType = true
		}
		t.Fields = append(t.Fields, &fieldsInfo{
			FieldName:   m.Capitalize(val.ColumnName),
			FieldType:   val.FieldType,
			FieldOri:    val.ColumnName,
			FieldTag:    m.formatField(val.ColumnName, tags),
			FieldRemark: m.addToComment(val.ColumnComment, ""),
		})
	}

	// 判断import是否加载过
	if m.checkFileContainsChar(req.EntityPath, "time") == false && fieldType {
		m.writeFile(req.EntityPath, s)
		writeTime = true
	} else if writeTime {
		m.writeFile(req.EntityPath, s2)
	}
	// 加载模板文件
	tplByte, err := f.ReadFile(tplEntity)
	if err != nil {
		log.Fatal("生成"+pkgDao+"层代码错误!>>", err)
	}

	// 创建一个名为 PkgEntity.tpl 的模板文件，并将字符串 tplByte 解析为模板,tplByte 里面需要填充的字段要严格按照template包规定的那样填写。
	tpl, err := template.New(pkgDao).Parse(string(tplByte))
	if err != nil {
		log.Fatal("生成"+pkgDao+"层代码错误!>>", err)
	}

	// 将解析好的模板应用到 t 上，并将输出写入 content
	content := bytes.NewBuffer([]byte{})
	tpl.Execute(content, t)

	// 表信息写入文件
	con := strings.Replace(content.String(), "&#34;", `"`, -1)
	err = m.writeFileAppend(req.EntityPath, con)
	if err != nil {
		log.Fatal("生成"+pkgDao+"层代码错误!>>", err)
	}
}

func (m *schema) genOneDao(req *entityReq) {
	s := fmt.Sprintf(`
package %s
`, pkgDao)
	req.EntityPath = m.createDir(m.path+pkgDao) + "/" + req.TableName + ".go"
	//req.EntityPath = m.createDir(m.path+pkgDao) + "/" + "dao" + ".go" (打开这一行注释,则所有的 CRUD 方法都生成在 dao.go 这个文件中)
	// 声明表结构变量，一个 TableInfo对象就是一个 Goland结构体
	t := &tableInfo{
		Table:        m.Capitalize(req.TableName),
		Table2:       req.TableName,
		TablePrefix:  req.TablePrefix,
		TableCamel:   m.Snake2Camel(req.TableName),
		TableComment: m.addToComment(req.TableComment, ""),
	}
	// 装载单个表的所有字段，将单个表的所有字段保存到 TableInfo.Fields 中。
	for _, val := range req.TableColumns {
		tags := req.TagKeys
		if val.ColumnName == "version" || val.ColumnName == "delete_flag" || val.ColumnName == "deleted" {
			tags = append(tags, "gm")
		}
		t.Fields = append(t.Fields, &fieldsInfo{
			FieldName:   m.Capitalize(val.ColumnName),
			FieldType:   val.FieldType,
			FieldOri:    val.ColumnName,
			FieldTag:    m.formatField(val.ColumnName, tags),
			FieldRemark: m.addToComment(val.ColumnComment, ""),
		})
	}
	m.writeFile(req.EntityPath, s)
	// 加载模板文件
	tplByte, err := f.ReadFile(tplDao)
	if err != nil {
		log.Fatal("生成"+pkgDao+"层代码错误!>>", err)
	}

	// 创建一个名为 PkgEntity.tpl 的模板文件，并将字符串 tplByte 解析为模板,tplByte 里面需要填充的字段要严格按照template包规定的那样填写。
	tpl, err := template.New(pkgDao).Parse(string(tplByte))
	if err != nil {
		log.Fatal("生成"+pkgDao+"层代码错误!>>", err)
	}

	// 将解析好的模板应用到 t 上，并将输出写入 content
	content := bytes.NewBuffer([]byte{})
	tpl.Execute(content, t)

	// 表信息写入文件
	con := strings.Replace(content.String(), "&#34;", `"`, -1)
	err = m.writeFileAppend(req.EntityPath, con)
	if err != nil {
		log.Fatal("生成"+pkgDao+"层代码错误!>>", err)
	}
}

func (m *schema) genDaoInit(list []table) {
	file := m.createDir(m.path+pkgDao+"/") + fileInit
	// 判断package是否加载过
	checkStr := "package " + pkgDao
	if m.checkFileContainsChar(file, checkStr) == false {
		m.writeFile(file, checkStr+"\n") // 如果没有加载过则，加载包名，即: "package " + PkgEntity
	}
	// 加载模板文件
	tplByte, err := f.ReadFile(tplInit)
	if err != nil {
		log.Fatal("生成 init.go文件错误!>>", err)
	}
	tpl, err := template.New("init").Parse(string(tplByte))
	if err != nil {
		log.Fatal("生成 init.go文件错误!>>", err)
	}
	// 解析
	content := bytes.NewBuffer([]byte{})
	err = tpl.Execute(content, list) //这个方法第二个参数就是要写入模版中的数据
	if err != nil {
		log.Fatal("生成 init.go文件错误!>>", err)
	}
	// 表信息写入文件
	err = m.writeFileAppend(file, content.String())
	if err != nil {
		log.Fatal("生成 init.go文件错误!>>", err)
	}
}

//// CreateFrame 生成mvc基础代码
//func (m *schema) CreateFrame() (err error) {
//	err = m.CreateRouter()
//	if err != nil {
//		log.Fatalln(err)
//		return err
//	}
//	err = m.CreateApi()
//	if err != nil {
//		log.Fatalln(err)
//		return err
//	}
//	err = m.CreateService()
//	if err != nil {
//		log.Fatalln(err)
//		return err
//	}
//	return nil
//}
//func (m *schema) CreateRouter() (err error) {
//	if cfg.Gen.Table != "" {
//		tables := strings.Split(cfg.Gen.Table, ",")
//		for _, v := range tables {
//			err = m.genRouter(data[v])
//			if err != nil {
//				log.Fatal("生成"+pkgRouter+"层代码错误!>>", err)
//				continue
//			}
//		}
//	} else {
//		for _, v := range data {
//			err = m.genRouter(v)
//			if err != nil {
//				log.Fatal("生成"+pkgRouter+"层代码错误!>>", err)
//				continue
//			}
//		}
//
//	}
//	return
//}
//
//func (m *schema) genRouter(req *entityReq) (err error) {
//	s := fmt.Sprintf(`
//package %s
//import (
//	"github.com/gin-gonic/gin"
//)
//`, pkgRouter)
//	req.EntityPath = m.createDir(m.path+pkgRouter) + "/" + fileRouter
//	// 判断import是否加载过
//	check := "github.com/gin-gonic/gin"
//	if m.checkFileContainsChar(req.EntityPath, check) == false {
//		m.writeFile(req.EntityPath, s)
//	}
//
//	// 声明表结构变量，一个 TableInfo对象就是一个 Goland结构体
//	t := &tableInfo{
//		Table:        m.Capitalize(req.TableName),
//		TableCamel:   req.TableName,
//		TableComment: m.addToComment(req.TableComment, ""),
//	}
//
//	// 判断表结构是否加载过
//	if m.checkFileContainsChar(req.EntityPath, "func "+t.Table+"(g *gin.RouterGroup)") == true {
//		log.Println(t.Table + "已经存在，请删除该结构体并重新生成!")
//		return
//	}
//
//	// 加载模板文件
//	tplByte, err := f.ReadFile(tplRouter)
//	if err != nil {
//		return
//	}
//
//	// 创建一个名为 PkgRouter 的模板文件，并将字符串 tplByte 解析为模板,tplByte 里面需要填充的字段要严格按照template包规定的那样填写。
//	tpl, err := template.New(pkgRouter).Parse(string(tplByte))
//	if err != nil {
//		return
//	}
//
//	// 将解析好的模板应用到 t 上，并将输出写入 content
//	content := bytes.NewBuffer([]byte{})
//	tpl.Execute(content, t)
//
//	// 表信息写入文件
//	con := strings.Replace(content.String(), "&#34;", `"`, -1)
//	err = m.writeFileAppend(req.EntityPath, con)
//	if err != nil {
//		return
//	}
//	return
//}
//
//func (m *schema) CreateService() (err error) {
//	if cfg.Gen.Table != "" {
//		tables := strings.Split(cfg.Gen.Table, ",")
//		for _, v := range tables {
//			err = m.genService(data[v])
//			if err != nil {
//				log.Fatal("生成"+pkgService+"层代码错误!>>", err)
//				continue
//			}
//		}
//	} else {
//		for _, v := range data {
//			err = m.genService(v)
//			if err != nil {
//				log.Fatal("生成"+pkgService+"层代码错误!>>", err)
//				continue
//			}
//		}
//	}
//	return nil
//}
//
//func (m *schema) genService(req *entityReq) (err error) {
//	s := fmt.Sprintf(`
//package %s
//`, pkgService) // req.EntityPkg
//	// 判断import是否加载过
//	req.EntityPath = m.createDir(m.path+pkgService) + "/" + req.TableName + ".go"
//	if m.checkFileContainsChar(req.EntityPath, pkgService) == false {
//		m.writeFile(req.EntityPath, s)
//	}
//
//	// 声明表结构变量，一个 TableInfo对象就是一个 Goland结构体
//	t := &tableInfo{
//		Table:        m.Capitalize(req.TableName),
//		TableCamel:   m.Snake2Camel(req.TableName),
//		TableComment: m.addToComment(req.TableComment, ""),
//	}
//
//	// 判断表结构是否加载过
//	if m.checkFileContainsChar(req.EntityPath, "type "+t.TableCamel+" struct") == true {
//		log.Println(t.TableCamel + "已经存在，请删除该结构体并重新生成!")
//		return
//	}
//
//	// 加载模板文件
//	tplByte, err := f.ReadFile(tplService)
//	if err != nil {
//		return
//	}
//
//	// 创建一个名为 "entity.tpl" 的模板文件，并将字符串 tplByte 解析为模板,tplByte 里面需要填充的字段要严格按照template包规定的那样填写。
//	tpl, err := template.New(pkgService).Parse(string(tplByte))
//	if err != nil {
//		return
//	}
//
//	// 将解析好的模板应用到 t 上，并将输出写入 content
//	content := bytes.NewBuffer([]byte{})
//	tpl.Execute(content, t)
//
//	// 表信息写入文件
//	con := strings.Replace(content.String(), "&#34;", `"`, -1)
//	err = m.writeFileAppend(req.EntityPath, con)
//	if err != nil {
//		return
//	}
//	return
//}
//
//func (m *schema) CreateApi() (err error) {
//	if cfg.Gen.Table != "" {
//		tables := strings.Split(cfg.Gen.Table, ",")
//		for _, v := range tables {
//			err = m.genApi(data[v])
//			if err != nil {
//				log.Fatal("生成"+pkgApi+"层代码错误!>>", err)
//				continue
//			}
//		}
//	} else {
//		for _, v := range data {
//			err = m.genApi(v)
//			if err != nil {
//				log.Fatal("生成"+pkgApi+"层代码错误!>>", err)
//				continue
//			}
//		}
//	}
//	err = m.genApiRouter()
//	if err != nil {
//		log.Fatal("生成"+pkgApi+"层代码错误!>>", err)
//	}
//	return nil
//}
//
//func (m *schema) genApi(req *entityReq) (err error) {
//	s := fmt.Sprintf(`
//package %s
//import (
//	"github.com/gin-gonic/gin"
//	"net/http"
//)
//`, pkgApi) // req.EntityPkg
//	// 判断import是否加载过
//	req.EntityPath = m.createDir(m.path+pkgApi) + "/" + req.TableName + ".go"
//	if m.checkFileContainsChar(req.EntityPath, pkgApi) == false {
//		m.writeFile(req.EntityPath, s)
//	}
//
//	// 声明表结构变量，一个 TableInfo对象就是一个 Goland结构体
//	t := &tableInfo{
//		Table:        m.Capitalize(req.TableName),
//		TableCamel:   m.Snake2Camel(req.TableName), //结构体首字母小写
//		TableComment: m.addToComment(req.TableComment, ""),
//	}
//
//	// 判断表结构是否加载过
//	if m.checkFileContainsChar(req.EntityPath, "type "+t.TableCamel+" struct") == true {
//		log.Println(t.TableCamel + "已经存在，请删除该结构体并重新生成!")
//		return
//	}
//
//	// 加载模板文件
//	tplByte, err := f.ReadFile(tplApi)
//	if err != nil {
//		return
//	}
//
//	// 创建一个名为 "entity.tpl" 的模板文件，并将字符串 tplByte 解析为模板,tplByte 里面需要填充的字段要严格按照template包规定的那样填写。
//	tpl, err := template.New(pkgApi).Parse(string(tplByte))
//	if err != nil {
//		return
//	}
//
//	// 将解析好的模板应用到 t 上，并将输出写入 content
//	content := bytes.NewBuffer([]byte{})
//	tpl.Execute(content, t)
//
//	// 表信息写入文件
//	con := strings.Replace(content.String(), "&#34;", `"`, -1)
//	err = m.writeFileAppend(req.EntityPath, con)
//	if err != nil {
//		return
//	}
//	return
//}
//
//func (m *schema) genApiRouter() (err error) {
//	s := fmt.Sprintf(`
//package %s
//import (
//	"github.com/gin-gonic/gin"
//)
//`, pkgApi) // req.EntityPkg
//	file := m.createDir(m.path+pkgApi) + "/" + "router" + ".go"
//	if m.checkFileContainsChar(file, pkgApi) == false {
//		m.writeFile(file, s)
//	}
//	// 加载模板文件
//	tplByte, err := f.ReadFile(tplHttp)
//	if err != nil {
//		return
//	}
//	tpl, err := template.New("router").Parse(string(tplByte))
//	if err != nil {
//		return
//	}
//	// 解析
//	content := bytes.NewBuffer([]byte{})
//	err = tpl.Execute(content, list) //这个方法第二个参数就是要写入模版中的数据
//	if err != nil {
//		return
//	}
//	// 表信息写入文件
//	err = m.writeFileAppend(file, content.String())
//	if err != nil {
//		return
//	}
//	return nil
//}
//
//func (m *schema) genToml() {
//	file := m.createDir(m.path+"/") + fileConfig
//	// 加载模板文件
//	tplByte, err := f.ReadFile(tplToml)
//	if err != nil {
//		log.Fatal("生成"+fileConfig+"文件错误!>>", err)
//	}
//	tpl, err := template.New("toml").Parse(string(tplByte))
//	if err != nil {
//		log.Fatal("生成"+fileConfig+"文件错误!>>", err)
//	}
//	// 解析
//	content := bytes.NewBuffer([]byte{})
//	err = tpl.Execute(content, list) //这个方法第二个参数就是要写入模版中的数据
//	if err != nil {
//		log.Fatal("生成"+fileConfig+"文件错误!>>", err)
//	}
//	// 表信息写入文件
//	err = m.writeFileAppend(file, content.String())
//	if err != nil {
//		log.Fatal("生成"+fileConfig+"文件错误!>>", err)
//	}
//}
